﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Sys = System.Timers;

namespace UpgradeLoader
{

    /// <summary>
    /// 1.最多同时下载5个文件
    /// 2、默认30s进行一次心跳访问
    /// 3、断线默认超过10分钟，我们会停止下载，然后保存下载参数到downInfo.json文件
    /// 4、停止下载后，恢复连接后，我们会恢复下载。
    /// </summary>
    public class DownloadCenter
    {
        private string heartBeatFile = "heart.xml";//请求心跳用，文件服务器必须要有这个文件

        private string downingJsonFile = "downInfo.json";//下载终止时，各个文件的下载情况保存到此json文件
        private Sys.Timer timer;
        /// <summary>
        /// 心跳断线为false
        /// </summary>
        private bool IsConnect = false;

        /// <summary>
        /// 下载服务器http地址
        /// </summary>
        private string server;

        /// <summary>
        /// 断线时间
        /// </summary>
        private DateTime lostTime;

        private DateTime onlineTime;

        /// <summary>
        /// 断线超时时间分钟
        /// </summary>
        private int downloadTimeoutMins = 10;

        /// <summary>
        /// 是否需要恢复
        /// </summary>
        private bool isNeedRecover = false;

        /// <summary>
        /// 需要下载的项目
        /// </summary>
        List<DownloadItem> downItems;

        /// <summary>
        /// 正在下载的项目
        /// </summary>
        List<DownloadItem> Downloading = new List<DownloadItem>();

        /// <summary>
        /// 下载完成的项目
        /// </summary>
        List<DownloadItem> FinishItems = new List<DownloadItem>();

        /// <summary>
        /// 更新配置
        /// </summary>
        SoftwareUpdate currentSett;

        /// <summary>
        /// 是否全部下载完成
        /// </summary>
        public bool IsDownFinished = false;

        private string serverRoot = string.Empty;

        public event Action<List<DownloadItem>>? DownloadFinishEventAsync;
        public DownloadCenter(SoftwareUpdate update, List<DownloadItem> needDowndItems)
        {
            currentSett = update;
            server = update.UpdateServer.PrimaryServerURL;
            serverRoot = server.Replace("upgradedir/", "");
            downItems = needDowndItems;
            timer = new Sys.Timer(30 * 1000);
            timer.Elapsed += Timer_Elapsed;
            lostTime = DateTime.Now;
            timer.Start();
        }

        public async Task StartDownload()
        {

            if (File.Exists(downingJsonFile))
            {//如果有没有下载完的文件，并且这个版本的文件比当前要下载的版本的文件的版本持平或大于，就用json文件里的文件替换下载
                //var jsonStr = File.ReadAllText(downingJsonFile);
                //var downInfo = JsonSerializer.Deserialize<DowningFile>(jsonStr)!;
                var downInfo = Util.ToObj<DowningFile>(downingJsonFile);
                //当前下载的文件在json配置里的文件不存在要加进去
                var notEixts = downItems.Where(a => downInfo.Items.Find(b => b.DestFileName.ToLower() != a.DestFileName.ToLower()) != null).ToList();

                if (downInfo.Version >= currentSett.VersionInfo.CurrentVersionNum)
                {
                    downItems = downInfo.Items;

                    if (notEixts.Count > 0)
                    {
                        downItems.AddRange(notEixts);
                    }
                }

            }

            var downingCount = Math.Min(5, downItems.Count);//最多同时下载5个文件
            List<Task<int>> tasks = new List<Task<int>>();
            while (isNeedRecover == false &&
                (downItems.Count > 0 || Downloading.Count > 0 || tasks.Count > 0))
            {
                if (tasks.Count == 0 && downItems.Count == 0)
                {
                    break;
                }
                if (Downloading.Count < downingCount && downItems.Count > 0)
                {
                    var item = downItems[0];
                    downItems.RemoveAt(0);
                    Downloading.Add(item);
                    var task = DownloadFileAsync(item);
                    tasks.Add(task);
                }
                else
                {
                    var arr = await Task.WhenAll(tasks);
                    if (Array.IndexOf(arr, -2) >= 0)
                    {
                        break;
                    }
                    tasks.Clear();
                    var finish = Downloading.Where(a => a.IsFinish).ToList();
                    FinishItems.AddRange(finish);
                    foreach (var item in finish)
                    {
                        Downloading.Remove(item);
                    }
                }
            }

            if (downItems.Count == 0 && Downloading.Count == 0)
            {
                IsDownFinished = true;//全部下载完成
                if (File.Exists(downingJsonFile))//删除临时json
                {
                    File.Delete(downingJsonFile);
                }
                DownloadFinishEventAsync?.Invoke(FinishItems);

            }
            else
            {   //中途没有下载完成，保存临时json文件
                Downloading.AddRange(downItems);
                var file = new DowningFile() { Version = currentSett.VersionInfo.CurrentVersionNum, Items = Downloading };
                var jsonStr = Util.ToJson(file);
                File.WriteAllText(downingJsonFile, jsonStr);
            }
        }

        public async Task<int> DownloadFileAsync(DownloadItem item)
        {
            string url = item.DownUrl;
            string localPath = item.LocalDir + item.TempFileName;
            var offset = (int)item.DownOffset;
            FileMode fileMode = FileMode.Create;
            if (offset > 0 && File.Exists(localPath))
            {

                fileMode = FileMode.Append;
            }
            else
            {
                offset = 0;
                fileMode = FileMode.Create;
            }

            using (HttpClient client = new HttpClient())
            {
                client.Timeout = TimeSpan.FromMinutes(downloadTimeoutMins);

                try
                {
                    using (HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
                    {
                        if (!response.IsSuccessStatusCode)
                        {
                            return -1;
                        }
                        using (Stream stream = await response.Content.ReadAsStreamAsync())
                        {
                            if (offset > 0)
                            {
                                stream.Seek(offset, SeekOrigin.Begin);
                            }

                            using (FileStream fileStream = new FileStream(localPath, fileMode, FileAccess.Write, FileShare.None, 8192, true))
                            {
                                byte[] buffer = new byte[8192];
                                int bytesRead;
                                long totalBytesRead = 0;
                                long totalBytes = response.Content.Headers.ContentLength ?? -1;
                                while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
                                {
                                    await fileStream.WriteAsync(buffer, 0, bytesRead);
                                    totalBytesRead += bytesRead;
                                    item.DownOffset = totalBytesRead;
                                    // 处理下载进度，例如更新UI
                                    //   Console.WriteLine($"下载进度：{totalBytesRead}/{totalBytes}");
                                }
                                item.IsFinish = true;
                            }
                        }
                        return 0;
                    }
                }
                catch (TaskCanceledException ex)
                {
                    return -2;
                }
            }
        }

        private void Timer_Elapsed(object? sender, Sys.ElapsedEventArgs e)
        {
            TickRequst();

            if (IsConnect == false)
            {
                if ((DateTime.Now - lostTime).TotalMinutes > downloadTimeoutMins)
                {
                    isNeedRecover = true;
                    // stopDownload();
                }
            }
            else
            {
                if (isNeedRecover == true)
                {
                    lostTime = DateTime.Now;
                    isNeedRecover = false;
                    if (IsDownFinished == false)
                    {
                        RecoverDownload();

                    }

                }
            }

        }

        /// <summary>
        /// 超过10分钟（downloadTimeoutMins）断线没连上，就会停止下载，然后比如20分钟后又连上了，我们就重新恢复下载
        /// </summary>
        /// <exception cref="Exception"></exception>
        private void RecoverDownload()
        {
            if (!File.Exists(downingJsonFile)) { throw new Exception("未找到下载终止的文件"); }

            var jsonStr = File.ReadAllText(downingJsonFile);
            try
            {
                DowningFile file = JsonSerializer.Deserialize<DowningFile>(jsonStr)!;
                var downingList = file.Items;

                if (downingList.Count > 0)
                {
                    StartDownload();
                }

            }
            catch (Exception ex)
            {

                throw ex;
            }

        }
        private void stopDownload()
        {

        }

        private void TickRequst()
        {
            HttpClient client = new HttpClient();
            client.Timeout = TimeSpan.FromSeconds(20);
            HttpRequestMessage requestHeart = new HttpRequestMessage(HttpMethod.Get, serverRoot + heartBeatFile);
            try
            {
                var response = client.Send(requestHeart);
                if (response.IsSuccessStatusCode)
                {
                    IsConnect = true;
                    onlineTime = DateTime.Now;
                }
                client.Dispose();
                response.Dispose();
                requestHeart.Dispose();
            }
            catch (TaskCanceledException ex)
            {
                IsConnect = false;
                lostTime = DateTime.Now;
            }
        }

        public bool CheckSumFiles(List<DownloadItem> list)
        {
            if (list.Count <= 0) return false;

            var updateFiles = currentSett.UpdatePackages.UpdateItems;
            string fileName = string.Empty;
            foreach (var item in updateFiles)
            {
                var checkItem = list.Find(a => a.DestFileName == item.FileName);
                if (checkItem == null) { throw new Exception($"{item.FileName}未被下载"); }
                ;
                fileName = checkItem.LocalDir + checkItem.TempFileName;
                if (File.Exists(fileName))
                {
                    if (Util.GetMD5HashFromFile(fileName) != item.MD5)
                    {
                        checkItem.IsDownSuccess = false;
                    }
                    else
                    {
                        checkItem.IsDownSuccess = true;
                    }
                }
                else
                {
                    throw new Exception($"文件{fileName}不存在");
                }
            }
            return true;
        }

        public static List<DownloadItem> ConvertToDownItem(List<UpdateItem> updateItems, string server, string saveDir)
        {
            List<DownloadItem> result = new List<DownloadItem>();
            updateItems.ForEach(item =>
            {
                var dItem = new DownloadItem()
                {
                    DownUrl = server + item.FileName,
                    TempFileName = "temp_" + item.FileName,
                    DestFileName = item.FileName,
                    LocalDir = saveDir,
                    IsFinish = false,
                    DownOffset = 0,
                    IsZip = item.Zip
                };
                result.Add(dItem);
            });
            return result;
        }

        internal void RetryDownload(List<DownloadItem> finds)
        {
            this.downItems.Clear();

            finds.ForEach(item =>
            {
                item.DownOffset = 0;
                item.IsFinish = false;
                File.Delete(item.LocalDir + item.TempFileName);
            });

            this.downItems.AddRange(finds);
            StartDownload();
        }
    }
}
